#include "dmdedup.h"

uint32_t calcHash(const char *buf) {
    uint32_t ret;
    memcpy(&ret, buf, sizeof(uint32_t));
    return ret;
}

uint32_t allocate_new_block(void) {
    return entry_cnt;
}

void traceback(void) {
    pr_alert("traceback");
}

int write_dedup(uint32_t lbn, const char *buf) {
    struct metadata_entry *entry;
    struct LBN2PBN *l2p, *new_l2p;
    struct fp2PBN *f2p, *new_f2p;

    // 计算哈希值
    uint32_t fp = calcHash(buf);

    // 根据fp查hash表
    f2p = rhashtable_lookup_fast(&f2p_rht, &fp, f2p_params);
    if (f2p == NULL)  {
        // 添加一个entry
        if (entry_cnt >= MAX_ENTRY - 1) {
            pr_alert("ref overflow!!!!!!");
            return -1;
        }

        l2p = rhashtable_lookup_fast(&l2p_rht, &lbn, l2p_params);
        if (l2p != NULL) {
            // 需要将原来的引用减一
            entries[l2p->entry_id].ref--;
        }

        // 新搞一个entry
        entry = &entries[entry_cnt];
        entry->pbn = allocate_new_block();
        entry->ref = 1;
        entry->fp = fp;
        bWriteDedup(entry->pbn, buf);

        // 添加LBN->PBN和fp->PBN
        new_l2p = kzalloc(sizeof(struct LBN2PBN), GFP_KERNEL);
        if (new_l2p == NULL) {
            printk("kzalloc return NULL\n");
            return -1;
        }
        new_f2p = kzalloc(sizeof(struct fp2PBN), GFP_KERNEL);
        if (new_f2p == NULL) {
            printk("kzalloc return NULL\n");
            return -1;
        }

        new_l2p->lbn = lbn;
        new_l2p->entry_id = entry_cnt;

        new_f2p->fp = fp;
        new_f2p->entry_id = entry_cnt;

        rhashtable_insert_fast(&l2p_rht, &new_l2p->node, l2p_params);
        rhashtable_insert_fast(&f2p_rht, &new_f2p->node, f2p_params);

        entry_cnt++;

    } else {
        
        l2p = rhashtable_lookup_fast(&l2p_rht, &lbn, l2p_params);
        if (l2p != NULL) {
            if (f2p->entry_id == l2p->entry_id) {
                // 相当于在原来的块上写一样的东西
                return BLKSZ;
            }

            // 减原来哈希值的引用
            entries[l2p->entry_id].ref--;

        }

        // 添加新的LBN->PBN
        new_l2p = kzalloc(sizeof(struct LBN2PBN), GFP_KERNEL);
        if (new_l2p == NULL) {
            printk("kzalloc return NULL\n");
            return -1;
        }
        new_l2p->entry_id = f2p->entry_id;
        new_l2p->lbn = lbn;
        rhashtable_insert_fast(&l2p_rht, &new_l2p->node, l2p_params);

        // 加当前哈希值的引用
        entries[f2p->entry_id].ref++;

    }
    return BLKSZ;
}

int read_dedup(uint32_t lbn, char *buf) {
    struct LBN2PBN *l2p;
    l2p = rhashtable_lookup_fast(&l2p_rht, &lbn, l2p_params);
    if (l2p == NULL) {
        memset(buf, 0, BLKSZ);
        return 0;
    }
    return bReadDedup(entries[l2p->entry_id].pbn, buf);
}

static int fs_init(void)
{
    int ret;
    char *buf, *buf1, *buf2;

    pr_alert("========================================\n");

    buf  = kzalloc(sizeof(char) * (BLKSZ + 1), GFP_KERNEL);
    buf1 = kzalloc(sizeof(char) * (BLKSZ + 1), GFP_KERNEL);
    buf2 = kzalloc(sizeof(char) * (BLKSZ + 1), GFP_KERNEL);

    // 初始化哈希表
    ret = rhashtable_init(&l2p_rht, &l2p_params);
    if (ret == -EINVAL){
        return -1;
    }
    ret = rhashtable_init(&f2p_rht, &f2p_params);
    if (ret == -EINVAL){
        return -1;
    }

    entries = kzalloc(sizeof(struct metadata_entry) * MAX_ENTRY, GFP_KERNEL);
    if (entries == NULL) {
        printk("kzalloc return NULL\n");
        goto err_exit;
    }
    entry_cnt = 0;

    // 简单功能测试 测试dmdedup的每一条写路径
    // 简单去重测试
    writeBoth(0, "1111111\n")
    writeBoth(1, "2222222\n")
    writeBoth(2, "1111111\n")
    readBoth(0)
    readBoth(1)
    readBoth(2)

    // 修改出新块
    writeBoth(1, "3333333\n")
    readBoth(1)

    // 修改出重复块
    writeBoth(1, "2222222\n")
    readBoth(1)

    // 原地不动
    writeBoth(2, "1111111\n")
    readBoth(2)

rderr:
err_exit:
    rhashtable_free_and_destroy_m(&l2p_rht)
    rhashtable_free_and_destroy_m(&f2p_rht)
    Kfree(entries);
    return 0;
}

static void fs_exit(void)
{
    printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(fs_init);
module_exit(fs_exit);

MODULE_AUTHOR("cscn");
MODULE_DESCRIPTION("Flash Friendly File System");
MODULE_LICENSE("GPL");